﻿
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace CatEars.Core.Numbers
{
    /// <summary>
    /// 中文数字转换
    /// </summary>
    public static class CnNumConv
    {
        /// <summary>
        /// 数字位置说明
        /// </summary>
        [DebuggerDisplay("{OrginValue} -> {NewValue} [{StartIndex}]")]
        public struct NumberLoc
        {
            /// <summary>
            /// 转换后的值
            /// </summary>
            public long NewValue;
            /// <summary>
            /// 原始值
            /// </summary>
            public string OrginValue;
            /// <summary>
            /// 起始位置（如果是替换的话则是相对与最终输出的字符串）
            /// </summary>
            public int StartIndex;
        }

        /// <summary>
        /// 值定义
        /// </summary>
        static Dictionary<char, long> m_dictValue;

        /// <summary>
        /// 位数定义
        /// </summary>
        static Dictionary<char, long> m_dictUnit;

        /// <summary>
        /// 亿
        /// </summary>
        static char[] m_numYi = new char[] { '亿' };

        /// <summary>
        /// 忽略转换的中文
        /// </summary>
        static HashSet<string> m_hsIgnoreConvert;

        /// <summary>
        /// 静态初始化转换类
        /// </summary>
        static CnNumConv()
        {
//            零	〇	标准汉字数字
//壹	一	
//贰	二	
//叁	三	
//肆	四	
//伍	五	
//陆	六	
//柒	七	
//捌	八	
//玖	九	
//拾	十	
//佰	百	
//仟	千	万亿兆吉太拍艾分厘毫微

            m_dictValue = new Dictionary<char, long>();
            //m_dictValue['0'] = 0;
            //m_dictValue['1'] = 1;
            //m_dictValue['2'] = 2;
            //m_dictValue['3'] = 3;
            //m_dictValue['4'] = 4;
            //m_dictValue['5'] = 5;
            //m_dictValue['6'] = 6;
            //m_dictValue['7'] = 7;
            //m_dictValue['8'] = 8;
            //m_dictValue['9'] = 9;
            m_dictValue['零'] = 0;
            m_dictValue['一'] = 1;
            m_dictValue['二'] = 2;
            //m_dictValue['两'] = 2;
            m_dictValue['三'] = 3;
            m_dictValue['四'] = 4;
            m_dictValue['五'] = 5;
            m_dictValue['六'] = 6;
            m_dictValue['七'] = 7;
            m_dictValue['八'] = 8;
            m_dictValue['九'] = 9;
            m_dictValue['〇'] = 0;
            m_dictValue['壹'] = 1;
            m_dictValue['贰'] = 2;
            m_dictValue['叁'] = 3;
            m_dictValue['肆'] = 4;
            m_dictValue['伍'] = 5;
            m_dictValue['陆'] = 6;
            m_dictValue['柒'] = 7;
            m_dictValue['捌'] = 8;
            m_dictValue['玖'] = 9;
            m_dictUnit = new Dictionary<char, long>();
            m_dictUnit['十'] = 10;
            m_dictUnit['百'] = 100;
            m_dictUnit['千'] = 1000;
            m_dictUnit['拾'] = 10;
            m_dictUnit['佰'] = 100;
            m_dictUnit['仟'] = 1000;
            m_dictUnit['万'] = 10000;
            m_dictUnit['亿'] = 100000000;

            m_hsIgnoreConvert = new HashSet<string>();
            m_hsIgnoreConvert.Add("亿");
            m_hsIgnoreConvert.Add("万");
            m_hsIgnoreConvert.Add("仟");
            m_hsIgnoreConvert.Add("佰");
            m_hsIgnoreConvert.Add("千");
            m_hsIgnoreConvert.Add("百");
            m_hsIgnoreConvert.Add("零");
        }

        /// <summary>
        /// 解析字符串中的数字
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <param name="lngValue">lngValue</param>
        /// <returns>返回值</returns>
        public static bool ParseNumber(
            string strInput,
            out long lngValue)
        {
            lngValue = 0;
            try
            {
                long? lngValue0 = ParseNumber(strInput, true);
                if (lngValue0.HasValue)
                {
                    lngValue = lngValue0.Value;
                    return true;
                }
            }
            catch
            {
                //该方法不抛出异常
            }
            return false;
        }

        /// <summary>
        /// 中文转数字
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <returns>返回值</returns>
        public static long ParseNumber(string strInput)
        {
            return ParseNumber(strInput, false).Value;
        }

        /// <summary>
        /// 中文转数字
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <param name="blnIgnoreErr">是否忽略错误</param>
        /// <returns>返回值</returns>
        private static long? ParseNumber(string strInput, bool blnIgnoreErr)
        {
            if (string.IsNullOrEmpty(strInput))
            {
                throw new ArgumentNullException("输入值无内容");
            }
            bool blnUnit = false;
            foreach (var strC in strInput)
            {
                if (m_dictUnit.ContainsKey(strC))
                {
                    blnUnit = true;
                    break;
                }
            }
            long lngValue;
            string strMsg = ParseNumber(strInput, blnUnit, out lngValue);
            if (strMsg != null)
            {
                if (blnIgnoreErr)
                {
                    return null;
                }
                else
                {
                    throw new ArgumentException(strMsg, "strInput");
                }
            }
            return lngValue;
        }

        /// <summary>
        /// 解析字符串中的数字
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <param name="blnWithUnit">blnWithUnit</param>
        /// <param name="lngValue">[输出]转换后的值</param>
        /// <returns>返回错误信息 null表示正确</returns>
        private static string ParseNumber(string strInput,
            bool blnWithUnit,
            out long lngValue)
        {
            lngValue = 0;
            if (blnWithUnit)
            {
                string[] strYis = strInput.Split(m_numYi);
                string strMsg;
                switch (strYis.Length)
                {
                    case 2:
                        if (strYis[0].Length + strYis[1].Length == 0)
                        {
                            lngValue = 100000000;
                        }
                        else
                        {
                            long lngValue1;
                            long lngValue2 = 0;
                            strMsg = ParseNumberWithUnit(strYis[0], out lngValue1);
                            if (strMsg != null)
                            {
                                return strMsg;
                            }
                            if (lngValue1 == 0)
                            {
                                return "亿前面无数字";
                            }
                            string strNextValue = strYis[1];
                            if (strNextValue.Length > 0)
                            {
                                long lngValue2Tmp;
                                m_dictValue.TryGetValue(strNextValue[0], out lngValue2Tmp);
                                bool blnStartWithZero = (lngValue2Tmp == 0);
                                if (strNextValue.Length == 1)
                                {
                                    if (blnStartWithZero)
                                    {
                                        return "亿后面内容无效";
                                    }
                                    lngValue2 = lngValue2Tmp * 10000000;
                                }
                                else
                                {
                                    strMsg = ParseNumberWithUnit(strNextValue, out lngValue2);
                                    if (strMsg != null)
                                    {
                                        return strMsg;
                                    }
                                    if (!blnStartWithZero && lngValue2 < 10000000)
                                    {
                                        return "亿后面不带千万级且不含0";
                                    }
                                }
                            }
                            lngValue = lngValue1 * 100000000 + lngValue2;
                        }
                        break;
                    case 1:
                        strMsg = ParseNumberWithUnit(strInput, out lngValue);
                        if (strMsg != null)
                        {
                            return strMsg;
                        }
                        break;
                    default:
                        return "包含2个亿字,超出支持范围";
                }
            }
            else
            {
                foreach (var strC in strInput)
                {
                    lngValue *= 10;
                    long lngIndex;
                    if (!m_dictValue.TryGetValue(strC, out lngIndex))
                    {
                        return "出现非数字字符";
                    }
                    if (lngIndex < 0)
                    {
                        return "出现非数字字符";
                    }
                    lngValue += lngIndex;
                }
            }
            return null;
        }

        /// <summary>
        /// 带位数的解析
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <param name="lngValue">[输出]转换后的值</param>
        /// <returns>返回错误信息 null表示正确</returns>
        private static string ParseNumberWithUnit(
            string strInput,
            out long lngValue)
        {
            bool blnWanFlag = false;
            lngValue = 0;
            //状态 0无 1数值 2位数
            int intState = 0;
            long? lngValueBase = null;
            long? lngValueUnit = null;
            long lngLastUnit = long.MaxValue;
            long lngFinishUnit = 1;
            if (m_hsIgnoreConvert.Contains(strInput))
            {
                return "出现忽略词";
            }
            foreach (var strC in strInput)
            {
                long lngValueFlag = 0;
                if (m_dictUnit.TryGetValue(strC, out lngValueFlag))
                {
                    if (intState == 0)
                    {
                        if (lngValueFlag == 10)
                        {
                            lngValueBase = 1;
                        }
                        else
                        {
                            return strC + "前面无数字";
                        }
                    }
                    if (lngValueFlag == 10000)
                    {
                        if (blnWanFlag)
                        {
                            return "万字重复了";
                        }
                        blnWanFlag = true;
                        if (lngValueBase.HasValue)
                        {
                            lngValue += lngValueBase.Value * (lngValueUnit ?? 1);
                        }
                        lngValue *= 10000;
                        lngValueBase = null;
                        lngValueUnit = null;
                        lngLastUnit = lngValueFlag;
                        lngFinishUnit = 1000;
                    }
                    else
                    {
                        //支持十前面的一省略
                        if (intState == 2 && lngValueFlag == 10)
                        {
                            if (lngValueBase.HasValue)
                            {
                                long lngUnit = lngValueUnit ?? 1;
                                lngValue += lngValueBase.Value * lngUnit;
                                lngFinishUnit = lngUnit >= 10 ? lngUnit / 10 : 1;
                                lngValueUnit = null;
                            }
                            lngValueBase = 1;
                            intState = 1;
                        }
                        if (intState == 2)
                        {
                            //出现连续2个位数
                            return "语义不通";
                        }
                        else
                        {
                            if (lngLastUnit < lngValueFlag)
                            {
                                return lngValueFlag + "出现在" + lngLastUnit + "之后";
                            }
                            lngValueUnit = lngValueFlag;
                            lngLastUnit = lngValueFlag;
                        }
                        intState = 2;
                    }
                }
                else if (m_dictValue.TryGetValue(strC, out lngValueFlag))
                {
                    if (intState == 1 && lngValueBase > 0)
                    {
                        return "出现连续2个数字";
                    }
                    intState = 1;
                    if (lngValueBase.HasValue)
                    {
                        long lngUnit = lngValueUnit ?? 1;
                        lngValue += lngValueBase.Value * lngUnit;
                        lngFinishUnit = lngUnit >= 10 ? lngUnit / 10 : 1;
                        lngValueUnit = null;
                    }
                    lngValueBase = lngValueFlag;
                    if (lngValueFlag == 0)
                    {
                        lngFinishUnit = 0;
                    }
                }
                else
                {
                    return "出现无效字符" + strC;
                }
            }
            if (lngValueBase.HasValue)
            {
                if (intState == 2)
                {
                    lngValue += lngValueBase.Value * (lngValueUnit ?? 1);
                }
                else
                {
                    lngValue += lngValueBase.Value * lngFinishUnit;
                }
            }
            return null;
        }
        /// <summary>
        /// 解析字符串中的数字并逐一替换
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <returns>返回值</returns>
        public static string ReplaceChNumToNum(
            string strInput)
        {
            string strOutput;
            ReplaceChNumToNum(strInput, out strOutput);
            return strOutput;
        }

        /// <summary>
        /// 解析字符串中的数字并逐一替换
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <param name="strOutput">strOutput</param>
        /// <returns>返回值</returns>
        public static IList<NumberLoc> ReplaceChNumToNum(
            string strInput, out string strOutput)
        {
            return GetChNumToNum(strInput, true, out strOutput);
        }

        /// <summary>
        /// 获取字符串中的数字
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <returns>返回值</returns>
        public static IList<NumberLoc> GetChNumToNum(
            string strInput)
        {
            string strTmp;
            return GetChNumToNum(strInput, false, out strTmp);
        }

        /// <summary>
        /// 获取字符串中的数字，并决定是否替换
        /// </summary>
        /// <param name="strInput">strInput</param>
        /// <param name="blnReplace">blnReplace</param>
        /// <param name="strOutput">strOutput</param>
        /// <returns>返回值</returns>
        private static IList<NumberLoc> GetChNumToNum(
            string strInput,
            bool blnReplace,
            out string strOutput)
        {
            strOutput = strInput;
            List<NumberLoc> result = new List<NumberLoc>();
            if (string.IsNullOrEmpty(strInput))
            {
                return result;
            }
            StringBuilder strBOutput = new StringBuilder();
            StringBuilder strBTmp = new StringBuilder();
            //1数字 2中文数字
            int intMode = 0;
            int? intIndexOffset = null;
            if (blnReplace)
            {
                intIndexOffset = 0;
            }
            for (int i = 0; i < strInput.Length; i++)
            {
                char chrC = strInput[i];
                if (chrC >= '0' && chrC <= '9')
                {
                    if (intMode != 1)
                    {
                        SaveValue(result, strBOutput, strBTmp, intMode, i, ref intIndexOffset);
                    }
                    intMode = 1;
                    strBTmp.Append(chrC);
                }
                else if (m_dictValue.ContainsKey(chrC) || m_dictUnit.ContainsKey(chrC))
                {
                    if (intMode != 2)
                    {
                        SaveValue(result, strBOutput, strBTmp, intMode, i, ref intIndexOffset);
                    }
                    intMode = 2;
                    strBTmp.Append(chrC);
                }
                else
                {
                    if (intMode != 0)
                    {
                        SaveValue(result, strBOutput, strBTmp, intMode, i, ref intIndexOffset);
                        intMode = 0;
                    }
                    strBOutput.Append(chrC);
                }
            }
            SaveValue(result, strBOutput, strBTmp, intMode, strInput.Length, ref intIndexOffset);
            if (blnReplace)
            {
                strOutput = strBOutput.ToString();
            }
            return result;
        }

        /// <summary>
        /// 进行字符转换和其它处理
        /// </summary>
        /// <param name="result">result</param>
        /// <param name="strBOutput">strBOutput</param>
        /// <param name="strBTmp">strBTmp</param>
        /// <param name="intMode">intMode</param>
        /// <param name="intIndex">intIndex</param>
        /// <param name="intIndexOffset">intIndexOffset</param>
        private static void SaveValue(List<NumberLoc> result,
            StringBuilder strBOutput,
            StringBuilder strBTmp,
            int intMode,
            int intIndex,
            ref int? intIndexOffset)
        {
            if (strBTmp.Length == 0)
            {
                return;
            }
            if (intMode == 1)
            {
                long lngValue;
                string strTmp = strBTmp.ToString();
                if (long.TryParse(strTmp, out lngValue))
                {
                    string strValue = lngValue.ToString();
                    NumberLoc num = new NumberLoc()
                    {
                        NewValue = lngValue,
                        OrginValue = strTmp,
                        StartIndex = intIndex - strTmp.Length + (intIndexOffset ?? 0),
                    };
                    result.Add(num);
                    strBOutput.Append(strValue);
                    intIndexOffset += strValue.Length - strTmp.Length;
                }
                else
                {
                    strBOutput.Append(strTmp);
                }
                strBTmp.Length = 0;
            }
            else if (intMode == 2)
            {
                long lngValue;
                string strTmp = strBTmp.ToString();
                if (CnNumConv.ParseNumber(strTmp, out lngValue))
                {
                    string strValue = lngValue.ToString();
                    NumberLoc num = new NumberLoc()
                    {
                        NewValue = lngValue,
                        OrginValue = strTmp,
                        StartIndex = intIndex - strTmp.Length + (intIndexOffset ?? 0),
                    };
                    result.Add(num);
                    strBOutput.Append(strValue);
                    intIndexOffset += strValue.Length - strTmp.Length;
                }
                else
                {
                    strBOutput.Append(strTmp);
                    strBTmp.Length = 0;
                }
                strBTmp.Length = 0;
            }
        }
    }
}
